#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <memory>
#include <string>

void Usage(std::string proc) {
  // 使用手册
  std::cout << "\n\tUsage: " << proc << " serverip serverport\n" << std::endl;
}

int main(int argc, char* argv[]) {
  // 判断环境变量参数是否符合使用标准
  if (argc != 3) {
    Usage(argv[0]);
    exit(0);
  }

  /*
    分解环境变量参数并进行转换为 IP 和端口
  */
  std::string serverip = argv[1];            // IP
  uint16_t serverport = std::stoi(argv[2]);  // 端口

  sockaddr_in local;             // 创建对应的地址族结构体
  bzero(&local, sizeof(local));  // 为结构体清零

  local.sin_family = AF_INET;  // 设置地址族为 IPv4

  local.sin_port = htons(serverport);  // 将端口号转换为网络字节序

  local.sin_addr.s_addr =
      inet_addr(serverip.c_str());  // 将IP地址转换为网络字节序并赋值

  socklen_t locallen = sizeof(local);  // 获取地址结构体的大小

  int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  // 创建UDP套接字并保存文件描述符

  if (sockfd < 0) {
    // 判断套接字是否创建成功
    std::cout << "socket fail" << std::endl;
    exit(-1);
  }
  std::cout << "socket success" << std::endl;

  // 通常客户端不需要显式绑定自己的IP和端口
  // 一个端口号只能被一个进程bind，客户端端口号只需保证唯一性

  std::string message;      // 用于保存发送给服务端的数据
  char buffer[1024] = {0};  // 用于接收服务端返回的数据

  while (true) {
    std::cout << "Please Enter@";  // 打印提示信息

    getline(std::cin, message);  // 读取用户输入的消息

    int sdebug = sendto(sockfd, message.c_str(), message.size(), 0,
                        (struct sockaddr*)&local, locallen);
    // 调用 sendto 向服务端发送数据

    if (sdebug < 0) {
      std::cout << "sendto fail, err: " << strerror(errno) << std::endl;
    }
    std::cout << "sendto success" << std::endl;  // 发送成功

    // -------------------------------

    struct sockaddr_in temp;     // 用于存储服务端的地址信息
    bzero(&temp, sizeof(temp));  // 清空结构体
    socklen_t len = sizeof(temp);
    ssize_t n =
        recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
    // 阻塞接收服务端的回应数据
    if (n > 0) {
      buffer[n] = 0;  // 将接收到的数据作为字符串处理
      std::cout << buffer << std::endl;
    }
  }

  close(sockfd);  // 关闭套接字文件描述符
  return 0;
}
